<?php declare(strict_types=1);

/**
 * @author      guosir
 * @copyright   2023-2025 owner
 **/

namespace Srv\Apps\Apis\Skit\Ctl;

use Exception;
use Google\Cloud\Storage\StorageClient;
use Srv\Apps\Apis\Common\Ctl\StorageCtl;
use Srv\Apps\Apis\Common\DefCtl\DefStorageCategoryCtl;
use Srv\Apps\Proto\DTO\Skit\SkitSeriesAddBatchDTO;
use Srv\Apps\Proto\DTO\Skit\SkitSeriesAddDTO;
use Srv\Apps\Proto\DTO\Skit\SkitSeriesAddItemDTO;
use Srv\Apps\Proto\DTO\Skit\SkitSeriesEditDTO;
use Srv\Apps\Proto\DTO\Skit\SkitSeriesExtItemDTO;
use Srv\Apps\Proto\DTO\Skit\SkitSeriesListDTO;
use Srv\Apps\Proto\Enum\Skit\SeriesTypeEnum;
use Srv\Apps\Proto\Enum\YesOrNoEnum;
use Srv\Apps\Proto\VO\Skit\SkitInfoVO;
use Srv\Apps\Proto\VO\Skit\SkitSeriesListVO;
use Srv\Apps\Proto\VO\Skit\SkitSeriesVO;
use Srv\Conf\ConfModel\StorageConf;
use Srv\Libs\Common\CommArray;
use Srv\Libs\Common\CommString;
use Srv\Libs\Common\CommTime;
use Srv\Libs\Frame\AuthData;
use Srv\Libs\Frame\ControllerAbstract;
use Srv\Libs\Frame\Page;
use Srv\Libs\Storage\DataMysql;

final class SkitSeriesCtl extends ControllerAbstract
{
    private ?DataMysql $DbAuto              = null;     // DB-Write

    /**
     * @return static
     * tag
     */
    final public static function tag():self
    {
        $thisObject                         = self::tagGet();
        if($thisObject instanceof self){
            $thisObject->DbAuto             = self::getDbAuto();
        }
        return $thisObject;
    }

    /**
     * @param int $skssId
     * @return array
     * getSkitSeriesSrc
     */
    public function getSkitSeriesSrc(int $skssId):array
    {
        if($skssId < 1) return [];
        $isFail     = false;
        $info       = $this->DbAuto->getDataOne('SELECT skss_id, skss_key, skio_id, skss_series, skss_name, skss_desc, skss_url, skss_time, skss_buy_price, skss_type, skss_sort, skss_issue, skss_like_user, skss_like_user_basic, skss_play_num, skss_create_time, skss_update_time FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE skss_id='.$skssId.' AND skss_delete=0', $isFail);
        if($isFail || !isset($info['skss_id'])) return [];
        return $info;
    }

    /**
     * @param array $skssIdList
     * @return array
     * getSkitSeriesSrcBatch
     */
    public function getSkitSeriesSrcBatch(array $skssIdList):array
    {
        if(count($skssIdList) < 1) return [];
        $isFail     = false;
        $list       = $this->DbAuto->getData('SELECT skss_id, skss_key, skio_id, skss_series, skss_name, skss_desc, skss_url, skss_time, skss_buy_price, skss_type, skss_sort, skss_issue, skss_like_user, skss_like_user_basic, skss_play_num, skss_create_time, skss_update_time FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE skss_id IN('.implode(',', $skssIdList).') AND skss_delete=0', $isFail);
        if($isFail || count($list) < 1) return [];
        $listData   = [];
        foreach ($list as $item){
            $skssIdItem             = intval($item['skss_id']??0);
            if($skssIdItem < 1) continue;
            $listData[$skssIdItem]  = $item;
        }
        return $listData;
    }

    /**
     * @param Page $Page
     * @param array $whereData
     * @return SkitSeriesListVO|null
     * getSkitSeriesList
     */
    public function getSkitSeriesList(Page $Page, array $whereData): ?SkitSeriesListVO
    {
        $limitString            = $Page->getLimitString();
        $whereString            = ' WHERE skss_delete=0 ';
        // 检索条件
        if(count($whereData) > 0)foreach ($whereData as $key => $val){
            switch ($key){
                case SkitSeriesListDTO::_skioId:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skio_id='. intval($val);
                    break;
                case SkitSeriesListDTO::_skssId:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skss_id='. intval($val);
                    break;
                case SkitSeriesListDTO::_seriesKey:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skss_key LIKE \'%'.$this->DbAuto->getRealEscapeString($val).'%\'';
                    break;
                case SkitSeriesListDTO::_series:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skss_series='. intval($val);
                    break;
                case SkitSeriesListDTO::_name:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skss_name LIKE \'%'.$this->DbAuto->getRealEscapeString($val).'%\'';
                    break;
                case SkitSeriesListDTO::_issue:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skss_issue='. intval($val);
                    break;
                case SkitSeriesListDTO::_type:
                    $whereString    .= (strlen($whereString)>0?' AND ':'').'skss_type='. intval($val);
                    break;
            }
        }
        $isFail     = false;
        $sql        = 'SELECT SQL_CALC_FOUND_ROWS skss_id, skss_key, skio_id, skss_series, skss_name, skss_desc, skss_url, skss_time, skss_buy_price, skss_type, skss_sort, skss_issue, skss_like_user, skss_like_user_basic, skss_play_num, skss_create_time, skss_update_time FROM '.$this->DbAuto->getTableNameAll('skit_series').$whereString.' ORDER BY skss_id DESC '.$limitString;
        $list       = $this->DbAuto->getData($sql, $isFail);
        $totalNum   = $this->DbAuto->getDataInt('SELECT FOUND_ROWS() as num', 'num');
        if($isFail) return null;
        $SkitSeriesVOList           = [];
        if(CommArray::isForeach($list)){
            $skioIdList             = array_unique(array_map('intval', array_column($list, 'skio_id')));
            $SkitInfoVOList         = SkitInfoCtl::tag()->getSkitInfoBatch($skioIdList);
            foreach($list as $info){
                $skssIdItem         = intval($info['skss_id']);
                if($skssIdItem < 1) continue;
                $SkitSeriesVO       = $this->getSkitSeriesVO($info);
                if(!($SkitSeriesVO instanceof SkitSeriesVO)) continue;
                $SkitInfoVO         = $SkitInfoVOList[intval($info['skio_id']??0)]??null;
                if($SkitInfoVO instanceof SkitInfoVO) $SkitSeriesVO->setSkitInfo($SkitInfoVO);
                $SkitSeriesVOList[] = $SkitSeriesVO;
            }
        }
        $SkitSeriesListVO           = new SkitSeriesListVO();
        $SkitSeriesListVO->setList($SkitSeriesVOList);
        $SkitSeriesListVO->setPage($Page->getPageVO($totalNum));
        return $SkitSeriesListVO;
    }

    /**
     * @param int $skssId
     * @return SkitSeriesVO|null
     * getSkitSeriesInfo
     */
    public function getSkitSeriesInfo(int $skssId):?SkitSeriesVO
    {
        $infoSrc    = $this->getSkitSeriesSrc($skssId);
        if(!isset($infoSrc['skss_id'])) return null;
        return $this->getSkitSeriesVO($infoSrc);
    }

    /**
     * @param array $skssIdList
     * @return array
     * getSkitSeriesInfoBatch
     */
    public function getSkitSeriesInfoBatch(array $skssIdList):array
    {
        $skitSeriesList = $this->getSkitSeriesSrcBatch($skssIdList);
        $listData       = [];
        if(CommArray::isForeach($skitSeriesList))foreach ($skitSeriesList as $item){
            $skssIdItem             = intval($item['skss_id']??0);
            if($skssIdItem < 1) continue;
            $listData[$skssIdItem]  = $this->getSkitSeriesVO($item);
        }
        return $listData;
    }

    /**
     * @param array $info
     * @return SkitSeriesVO|null
     * getSkitSeriesVO
     */
    public function getSkitSeriesVO(array $info):?SkitSeriesVO
    {
        $skssId             = intval($info['skss_id']??0);
        if($skssId < 1) return null;
        $SkitSeriesVO       = new SkitSeriesVO();
        $SkitSeriesVO->setSkssId($skssId);
        $SkitSeriesVO->setSkioId(intval($info['skio_id']));
        $SkitSeriesVO->setSeries(intval($info['skss_series']));
        $SkitSeriesVO->setKey(trim($info['skss_key']));
        $SkitSeriesVO->setName(trim($info['skss_name']));
        $SkitSeriesVO->setDesc(trim($info['skss_desc']));
        $SkitSeriesVO->setUrl($this->genPlayUrl(trim($info['skss_url'])));
        $SkitSeriesVO->setTime(intval($info['skss_time']));
        $SkitSeriesVO->setBuyPrice(intval($info['skss_buy_price']));
        $SkitSeriesVO->setType(SeriesTypeEnum::parseValue($info['skss_type']));
        $SkitSeriesVO->setSort(intval($info['skss_sort']));
        $SkitSeriesVO->setIssue(YesOrNoEnum::parseValue($info['skss_issue']));
        $SkitSeriesVO->setLikeUser(intval($info['skss_like_user']));
        $SkitSeriesVO->setLikeUserBasic(intval($info['skss_like_user_basic']));
        $SkitSeriesVO->setPlayNum(intval($info['skss_play_num']));
        $SkitSeriesVO->setCreateTime(intval($info['skss_create_time']));
        $SkitSeriesVO->setUpdateTime(intval($info['skss_update_time']));
        return $SkitSeriesVO;
    }

    /**
     * @param array $setData
     * @return int
     * skitSeriesAdd
     */
    public function skitSeriesAdd(array $setData):int
    {
        if(count($setData) < 1) return 0;
        $setString      = '';
        $skioId         = 0;
        foreach ($setData as $key => $val){
            switch ($key){
                case SkitSeriesAddDTO::_skioId:
                    $skioId         = intval($val);
                    $skitInfoSrc    = SkitInfoCtl::tag()->getSkitInfoSrc($skioId);
                    if(!isset($skitInfoSrc['skio_id'])) return 0;
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skio_id='.$skioId;
                    break;
                case SkitSeriesAddDTO::_series:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_series='.intval($val);
                    break;
                case SkitSeriesAddDTO::_name:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_name=\''.$this->DbAuto->getRealEscapeString($val).'\'';
                    break;
                case SkitSeriesAddDTO::_desc:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_desc=\''.$this->DbAuto->getRealEscapeString($val).'\'';
                    break;
                case SkitSeriesAddDTO::_urlKey:
                    $dsacId         = DefStorageCategoryCtl::tag()->getIdByKey(DefStorageCategoryCtl::KEY_VIDEO_RES);
                    if($dsacId < 1) return 0;
                    $saveUrl        = StorageCtl::tag()->getStorageUrlRecord(AuthData::getLoginUserId(), $dsacId, $val);
                    if(strlen($saveUrl) < 1) return 0;
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_url=\''.$this->DbAuto->getRealEscapeString($saveUrl).'\', skss_url_ori=\''.$this->DbAuto->getRealEscapeString($saveUrl).'\', skss_url_handle=0';
                    break;
                case SkitSeriesAddDTO::_time:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_time='.intval($val);
                    break;
                case SkitSeriesAddDTO::_buyPrice:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_buy_price='.intval($val);
                    break;
                case SkitSeriesAddDTO::_issue:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_issue='.intval($val);
                    break;
                case SkitSeriesAddDTO::_type:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_type='.intval($val);
                    break;
                case SkitSeriesAddDTO::_sort:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_sort='.intval($val);
                    break;
            }
        }
        if(strlen($setString) < 1) return 0;
        $currTime   = CommTime::getTimeStamp(0);
        $seriseKey  = $this->getSkitSeriesKey();
        $isFail     = false;
        $sql        = 'INSERT INTO '.$this->DbAuto->getTableNameAll('skit_series').' SET '.$setString.', skss_key=\''.$this->DbAuto->getRealEscapeString($seriseKey).'\', skss_delete=0, skss_create_time='.$currTime.', skss_update_time='.$currTime;
        $skssId     = $this->DbAuto->getDataId($sql, $isFail);
        if($isFail || $skssId < 1) return 0;
        $this->flushSkitInfoTotalNum($skioId);
        return $skssId;
    }

    /**
     * @param array $setData
     * @param array $errSeries
     * @return bool
     * skitSeriesAddBatch
     */
    public function skitSeriesAddBatch(array $setData, array &$errSeries):bool
    {
        if(count($setData) < 1) return false;
        $skioId                 = intval($setData[SkitSeriesAddBatchDTO::_skioId]??0);
        $seriesList             = $setData[SkitSeriesAddBatchDTO::_series]??[];
        if($skioId < 1 || count($seriesList) < 1) return false;
        $skitInfoSrc            = SkitInfoCtl::tag()->getSkitInfoSrc($skioId);
        if(!isset($skitInfoSrc['skio_id'])) return false;
        $dsacId                 = DefStorageCategoryCtl::tag()->getIdByKey(DefStorageCategoryCtl::KEY_VIDEO_RES);
        if($dsacId < 1) return false;
        $isFlush                = false;
        $StorageCtl             = StorageCtl::tag();
        $seriesNumList          = array_column($seriesList, SkitSeriesAddItemDTO::_series);
        $sql                    = 'SELECT skss_id, skss_series FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE skio_id='.$skioId.' AND skss_series IN('.implode(',', $seriesNumList).') AND skss_delete=0';
        $list                   = $this->DbAuto->getData($sql);
        $seriesNumExitList      = array_column($list,'skss_id', 'skss_series');
        foreach ($seriesList as $item){
            if(!is_array($item)) continue;
            $currTime           = CommTime::getTimeStamp(0);
            if(!isset($item[SkitSeriesAddItemDTO::_series])) return false;
            $seriesItem         = intval($item[SkitSeriesAddItemDTO::_series]);
            $isEdit             = isset($seriesNumExitList[$seriesItem]);
            $nameItem           = $item[SkitSeriesAddItemDTO::_name]??'';
            $descItem           = $item[SkitSeriesAddItemDTO::_desc]??'';
            $urlKeyItem         = $item[SkitSeriesAddItemDTO::_urlKey]??'';
            $timeItem           = intval($item[SkitSeriesAddItemDTO::_time]??0);
            $buyPriceItem       = intval($item[SkitSeriesAddItemDTO::_buyPrice]??0);
            $issueItem          = YesOrNoEnum::parseValue($item[SkitSeriesAddItemDTO::_issue]??0);
            $typeItem           = SeriesTypeEnum::parseValue($item[SkitSeriesAddItemDTO::_type]??1);
            $sortItem           = intval($item[SkitSeriesAddItemDTO::_sort]??999999);
            if($seriesItem < 0 || strlen($urlKeyItem) < 1 || $timeItem < 1 || $buyPriceItem < 0){
                $errSeries[]    = $seriesItem;
                continue;
            }
            $saveUrl            = $StorageCtl->getStorageUrlRecord(AuthData::getLoginUserId(), $dsacId, $urlKeyItem);
            if(strlen($saveUrl) < 1){
                $errSeries[]    = $seriesItem;
                continue;
            }
            $setString          = 'skss_series='.$seriesItem.', skss_name=\''.$this->DbAuto->getRealEscapeString($nameItem).'\', skss_desc=\''.$this->DbAuto->getRealEscapeString($descItem).'\', skss_url=\''.$this->DbAuto->getRealEscapeString($saveUrl).'\', skss_url_ori=\''.$this->DbAuto->getRealEscapeString($saveUrl).'\', skss_url_handle=0, skss_time='.$timeItem.', skss_buy_price='.$buyPriceItem.', skss_issue='.$issueItem.', skss_type='.$typeItem.', skss_sort='.$sortItem;
            if($isEdit){
                $sql            = 'UPDATE '.$this->DbAuto->getTableNameAll('skit_series').' SET '.$setString.', skss_version=skss_version+1, skss_update_time='.$currTime.' WHERE skio_id='.$skioId.' AND skss_series='.$seriesItem.' AND skss_delete=0';
                $result         = $this->DbAuto->getDataNum($sql) > 0;
            }else{
                $isFail         = false;
                $seriseKey      = $this->getSkitSeriesKey();
                $sql            = 'INSERT INTO '.$this->DbAuto->getTableNameAll('skit_series').' SET '.$setString.', skss_version=0, skio_id='.$skioId.', skss_key=\''.$this->DbAuto->getRealEscapeString($seriseKey).'\', skss_delete=0, skss_create_time='.$currTime.', skss_update_time='.$currTime;
                $skssId         = $this->DbAuto->getDataId($sql, $isFail);
                $result         = !$isFail && $skssId > 0;
            }
            if(!$result){
                $errSeries[]    = $seriesItem;
                continue;
            }
            if(!$isFlush) $isFlush = true;
        }
        if($isFlush) $this->flushSkitInfoTotalNum($skioId);
        return true;
    }

    /**
     * @param int $skssId
     * @param array $setData
     * @param int $skioIdOld
     * @return bool
     * skitSeriesEdit
     */
    public function skitSeriesEdit(int $skssId, array $setData, int $skioIdOld):bool
    {
        if($skssId < 1 || count($setData) < 1) return false;
        $setString      = '';
        $skioId         = 0;
        $needFlush      = false;
        foreach ($setData as $key => $val){
            switch ($key){
                case SkitSeriesEditDTO::_skioId:
                    $skioId         = intval($val);
                    $skitInfoSrc    = SkitInfoCtl::tag()->getSkitInfoSrc($skioId);
                    if(!isset($skitInfoSrc['skio_id'])) return false;
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skio_id='.$skioId;
                    break;
                case SkitSeriesEditDTO::_series:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_series='.intval($val);
                    break;
                case SkitSeriesEditDTO::_name:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_name=\''.$this->DbAuto->getRealEscapeString($val).'\'';
                    break;
                case SkitSeriesEditDTO::_desc:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_desc=\''.$this->DbAuto->getRealEscapeString($val).'\'';
                    break;
                case SkitSeriesEditDTO::_urlKey:
                    $dsacId         = DefStorageCategoryCtl::tag()->getIdByKey(DefStorageCategoryCtl::KEY_VIDEO_RES);
                    if($dsacId < 1) return false;
                    $saveUrl        = StorageCtl::tag()->getStorageUrlRecord(AuthData::getLoginUserId(), $dsacId, $val);
                    if(strlen($saveUrl) < 1) return false;
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_url=\''.$this->DbAuto->getRealEscapeString($saveUrl).'\', skss_url_ori=\''.$this->DbAuto->getRealEscapeString($saveUrl).'\', skss_url_handle=0';
                    break;
                case SkitSeriesEditDTO::_time:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_time='.intval($val);
                    break;
                case SkitSeriesEditDTO::_buyPrice:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_buy_price='.intval($val);
                    break;
                case SkitSeriesEditDTO::_issue:
                    $needFlush      = true;
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_issue='.intval($val);
                    break;
                case SkitSeriesEditDTO::_type:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_type='.intval($val);
                    break;
                case SkitSeriesEditDTO::_sort:
                    $setString      .= (strlen($setString) > 0 ? ', ' : '') . 'skss_sort='.intval($val);
                    break;
            }
        }
        if(strlen($setString) < 1) return false;
        $isFail     = false;
        $currTime   = CommTime::getTimeStamp(0);
        $sql        = 'UPDATE '.$this->DbAuto->getTableNameAll('skit_series').' SET '.$setString.', skss_update_time='.$currTime.' WHERE skss_id='.$skssId.' AND skss_delete=0';
        if($this->DbAuto->getDataNum($sql, $isFail) < 1 || $isFail) return false;
        if($needFlush){
            $skioId = $skioId < 1 ? $skioIdOld: $skioId;
            $this->flushSkitInfoTotalNum($skioId);
        }
        return true;
    }

    /**
     * @param int $skioId
     * @return bool
     * flushSkitInfoTotalNum
     */
    public function flushSkitInfoTotalNum(int $skioId):bool
    {
        if($skioId < 1) return false;
        $skioTotalNum   = $this->DbAuto->getDataInt('SELECT COUNT(skss_id) as num FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE skio_id='.$skioId.' AND skss_delete=0 AND skss_issue=1', 'num');
        $skioRealNum    = $this->DbAuto->getDataInt('SELECT COUNT(skss_id) as num FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE skio_id='.$skioId.' AND skss_type=1 AND skss_delete=0 AND skss_issue=1', 'num');
        $this->DbAuto->getDataNum('UPDATE '.$this->DbAuto->getTableNameAll('skit_info').' SET skio_total_num='.$skioTotalNum.', skio_real_num='.$skioRealNum.' WHERE skio_id='.$skioId);
        return true;
    }

    /**
     * @param int $skioId
     * @return bool
     * flushSkitInfoLikeUser
     */
    public function flushSkitInfoLikeUser(int $skioId):bool
    {
        if($skioId < 1) return false;
        $skioLikeUser   = $this->DbAuto->getDataInt('SELECT SUM(skss_like_user + skss_like_user_basic) as like_num FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE skio_id='.$skioId.' AND skss_delete=0', 'like_num');
        return $this->DbAuto->query('UPDATE '.$this->DbAuto->getTableNameAll('skit_info').' SET skio_like_user='.$skioLikeUser.' WHERE skio_id='.$skioId);
    }

    /**
     * @return string
     * getSkitSeriesKey
     */
    public function getSkitSeriesKey():string
    {
        $minLength          = 8;        // 最小长度
        $maxLength          = 16;       // 最大长度
        $maxNum             = 200;
        $currNum            = 1;
        $indexNum           = 1;
        $skitSeriesKey      = strtolower(CommString::genRandStr($minLength));
        while($currNum <= $maxNum){
            $skssIdFind     = $this->DbAuto->getDataInt('SELECT skss_id FROM '.$this->DbAuto->getTableNameAll('skit_series').' WHERE binary skss_key=\''.$this->DbAuto->getRealEscapeString($skitSeriesKey).'\'', 'skss_id');
            if($skssIdFind < 1) break;
            if($indexNum >= 50){                // 失败自增长度1
                $indexNum   = 0;
                $minLength  += 1;
            }
            if($minLength > $maxLength) break;  // 长度封顶
            $skitSeriesKey  = strtolower(CommString::genRandStr($minLength));
            ++$indexNum;
            ++$currNum;
        }
        return $skitSeriesKey;
    }

    /**
     * @param string $playUrlSrc
     * @return string
     * genPlayUrl
     */
    private function genPlayUrl(string $playUrlSrc):string
    {
        if(strlen($playUrlSrc) < 1) return '';
        $StorageConf        = null;
        $StorageCtl         = StorageCtl::tag();
        $playUrl            = $StorageCtl->getStorageUrlFile($playUrlSrc, true, $StorageConf);
        $isCdn              = strtolower(substr($playUrl, strrpos($playUrl, '.'))) === '.m3u8';
        $ttl                = $StorageConf->getFirebaseCustomTokenTtl();
        if($ttl < 1) $ttl   = 3600;
        $currTime           = CommTime::getTimeStamp(0);
        if($isCdn){
            $appName        = ($StorageConf instanceof StorageConf) ? $StorageConf->getAppName() : '';
            // https://storage.googleapis.com/hsvod/ 替换成 https://tv-vod.yawentv.com
            $playUrl        = str_replace('https://storage.googleapis.com/'.$appName.'/', 'https://xctv-vod.xc.com/', $playUrl);
            $expireTime     = $currTime + $ttl;
            $keyName        = 'cdn-xctv-vod';
            $keyBase64      = 'mSXobFZhTPtCT3RCMQYunw==';
            $playUrl        = $this->genGgCdnUrl($playUrl, $keyName, $keyBase64, $expireTime);
        }else{
            $path           = '';
            $bucketName     = '';
            $appId          = '';
            if($StorageCtl->formatStorageUrl($playUrlSrc, $path, $bucketName, $appId) && ($StorageConf instanceof StorageConf)){
                try{
                    $StorageClient  = new StorageClient(['keyFilePath' => $StorageConf->getSecretKeyFile()]);
                    return $StorageClient->bucket($StorageConf->getAppName())->object(ltrim($path, '/'))->signedUrl($currTime + $ttl, ['method' => 'GET', 'version' => 'v4']);
                }catch (Exception $Exception){
                    return $playUrl;
                }
            }
        }
        return $playUrl;
    }

    /**
     * @param string $playUrl
     * @param string $keyName
     * @param string $keyBase64
     * @param int $expireEndTime
     * @return string
     * genGgCdnUrl
     */
    private function genGgCdnUrl(string $playUrl, string $keyName, string $keyBase64, int $expireEndTime):string
    {
        $keyBase64      .= str_repeat('=', (4 - strlen($keyBase64) % 4) % 4);
        $decodedKey     = base64_decode(strtr($keyBase64, '-_', '+/'), true);
        $signType       = strtolower(substr($playUrl, strrpos($playUrl, '.'))) === '.m3u8' ? 'path' : 'query';
        // https://cloud.google.com/media-cdn/docs/generate-signatures#mediacdn_sign_url_prefix-python
        switch ($signType){
            case 'path':{   // m3u8
                $prefixPos          = strrpos($playUrl, '/');
                $playUrlFileName    = substr($playUrl, $prefixPos + 1);
                $playUrlPrefix      = substr($playUrl, 0, $prefixPos + 1);
                $playUrlRes         = $playUrlPrefix.'edge-cache-token=Expires='.$expireEndTime.'&KeyName='.$keyName;
                $signature          = hash_hmac('sha1', $playUrlRes, $decodedKey, true);
                return $playUrlRes.'&Signature='.strtr(base64_encode($signature), '+/', '-_').'/'.$playUrlFileName;
            }
            default:{       // mp4
                $separator          = (strpos($playUrl, '?') === false) ? '?' : '&';
                $playUrlRes         = $playUrl.$separator.'Expires='.$expireEndTime.'&KeyName='.$keyName;
                $signature          = hash_hmac('sha1', $playUrlRes, $decodedKey, true);
                return $playUrlRes.'&Signature='.strtr(base64_encode($signature), '+/', '-_');
            }
        }
    }

    /**
     * @param array $setData
     * @param array $oldData
     * @return bool
     * checkExtData
     */
    public function checkExtData(array $setData, array &$oldData):bool
    {
        if(count($setData) < 1) return false;
        $skssIdList         = [];
        foreach ($setData as $item){
            if(!isset($item[SkitSeriesExtItemDTO::_skssId]) || !isset($item[SkitSeriesExtItemDTO::_likeUserBasic]) || intval($item[SkitSeriesExtItemDTO::_skssId]) < 1) return false;
            $skssIdList[]   = $item[SkitSeriesExtItemDTO::_skssId];
        }
        $skssIdListSize     = count($skssIdList);
        $oldDataOri         = $this->getSkitSeriesSrcBatch($skssIdList);
        if(count($oldDataOri) !== $skssIdListSize) return false;
        foreach ($skssIdList as $skssIdItem){
            if(!isset($oldDataOri[$skssIdItem])) return false;
            $oldData[]      = ['skssId' => $skssIdItem, 'skioId' => intval($oldDataOri[$skssIdItem]['skio_id']), SkitSeriesExtItemDTO::_likeUserBasic => intval($oldDataOri[$skssIdItem]['skss_like_user_basic'])];
        }
        if(count($oldData) !== $skssIdListSize) return false;
        return true;
    }

    /**
     * @param array $setData
     * @param array $skioIdList
     * @return bool
     * skitSeriesExtEdit
     */
    public function skitSeriesExtEdit(array $setData, array $skioIdList):bool
    {
        if(count($setData) < 1 || count($skioIdList) < 1) return false;
        $setString      = '';
        $skssIdList     = [];
        foreach ($setData as $item){
            $skssIdItem             = intval($item[SkitSeriesExtItemDTO::_skssId]??0);
            if($skssIdItem < 1) return false;
            $likeUserBasicItem      = intval($item[SkitSeriesExtItemDTO::_likeUserBasic]??0);
            $setString              .= ' WHEN '.$skssIdItem.' THEN '.$likeUserBasicItem;
            $skssIdList[]           = $skssIdItem;
        }
        if(strlen($setString) < 1 || count($skssIdList) < 1) return false;
        $currTime       = CommTime::getTimeStamp();
        $sql            = 'UPDATE '.$this->DbAuto->getTableNameAll('skit_series').' SET skss_like_user_basic=CASE skss_id '.$setString.' END, skss_update_time='.$currTime.' WHERE skss_id IN('.implode(',', $skssIdList).') AND skss_delete=0';
        $result         = $this->DbAuto->getDataNum($sql)>0;
        if($result){
            foreach ($skioIdList as $skioIdItem){
                if($skioIdItem > 0) $this->flushSkitInfoLikeUser($skioIdItem);
            }
        }
        return $result;
    }
}